package xxl.ioc.reflection 
{
	import flash.utils.describeType;
	import flash.utils.Dictionary;
	import flash.utils.getQualifiedClassName;
	import xxl.ioc.typedescriptions.ConstructorInjectionPoint;
	import xxl.ioc.typedescriptions.MethodInjectionPoint;
	import xxl.ioc.typedescriptions.NoParamsConstructorInjectionPoint;
	import xxl.ioc.typedescriptions.PostConstructInjectionPoint;
	import xxl.ioc.typedescriptions.PreDestroyInjectionPoint;
	import xxl.ioc.typedescriptions.PropertyInjectionPoint;
	import xxl.ioc.typedescriptions.TypeDescription;
	/**
	 * ...
	 * @author XXL
	 */
	public class DescribeTypeReflector extends ReflectorBase implements IReflector 
	{
		private var _currentFactoryXML:XML;
		
		private function addCtorInjectionPoint(description:TypeDescription, type:Class):void
		{
			const node:XML = _currentFactoryXML.constructor[0];
			if (!node)
			{
				if (_currentFactoryXML.parent().@name == "Object" || _currentFactoryXML.extendsClass.length() > 0)
				{
					description.ctor = new NoParamsConstructorInjectionPoint();
				}
				return;
			}
			const injectParameters:Dictionary = extractNodeParameters(node.parent().metadata.arg);
			const parameterNames:Array = (injectParameters.name || "").split(",");
			const parameterNodes:XMLList = node.parameter;
			
			/**
			 * 据说在FlashPlayer里面，除非该类被创建过，不然constructors里面的type都是*
			 */
			if (parameterNodes.(@type == "*").length() == parameterNodes.@type.length())
			{
				createDummyInstance(node, type);
			}
			const returnGMP:GatherMethodParametersReturn = gatherMethodParameters(parameterNodes, parameterNames);
			const parameters:Array = returnGMP.parameters;
			const requiredParameters:int = returnGMP.requiredParameters;
			description.ctor = new ConstructorInjectionPoint(parameters, requiredParameters, injectParameters);
		}
		
		private function addFieldInjectionPoints(description:TypeDescription):void
		{
			for each(var node:XML in _currentFactoryXML.*.(name() == "variable" || name() == "accessor").metadata.(@name == "Inject"))
			{
				const mappingId:String = node.parent().@type + "|" + node.arg.(@key == "name").attribute("value");
				const propertyName:String = node.parent().@name;
				const injectParameters:Dictionary = extractNodeParameters(node.arg);
				const injectionPoint:PropertyInjectionPoint = new PropertyInjectionPoint(mappingId, propertyName, injectParameters.optional == "true", injectParameters);
				description.addInjectionPoint(injectionPoint);
			}
		}
		
		private function addMethodInjectionPoints(description:TypeDescription):void
		{
			for each(var node:XML in _currentFactoryXML.method.metadata.(@name == "Inject"))
			{
				const injectParameters:Dictionary = extractNodeParameters(node.arg);
				const parameterNames:Array = (injectParameters.name || "").split(",");
				const returnGMP:GatherMethodParametersReturn = gatherMethodParameters(node.parent().parameter, parameterNames);
				const parameters:Array = returnGMP.parameters;
				const requiredParameters:int = returnGMP.requiredParameters;
				const injectionPoint:MethodInjectionPoint = new MethodInjectionPoint(node.parent().@name
					, parameters
					, requiredParameters
					, injectParameters.optional == "true"
					, injectParameters);
				description.addInjectionPoint(injectionPoint);
			}
		}
		
		private function addPostConstructMethodPoints(description:TypeDescription):void
		{
			const injectionPoints:Array = gatherOrderedInjectionPointsForTag(PostConstructInjectionPoint, "PostConstruct");
			const len:int = injectionPoints.length;
			for (var i:int = 0; i < len; i++)
			{
				description.addInjectionPoint(injectionPoints[i]);
			}
		}
		
		private function addPreDestroyMethodPoints(description:TypeDescription):void
		{
			const injectionPoints:Array = gatherOrderedInjectionPointsForTag(PreDestroyInjectionPoint, "PreDestroy");
			if (!injectionPoints.length)
			{
				return;
			}
			description.preDestoryMethods = injectionPoints[0];
			description.preDestoryMethods.last = injectionPoints[0];
			const len:int = injectionPoints.length;
			for (var i:int = 1; i < len; i++) 
			{
				description.preDestoryMethods.last.next = injectionPoints[i];
				description.preDestoryMethods.last = injectionPoints[i];
			}
		}
		
		private function extractNodeParameters(args:XMLList):Dictionary
		{
			const parametersMap:Dictionary = new Dictionary();
			const len:int = args.length();
			for (var i:int = 0; i < len; i++) 
			{
				const parameter:XML = args[i];
				const key:String = parameter.@key;
				parametersMap[key] = parametersMap[key] ? parametersMap[key] + "," + parameter.attribute("value") : parameter.attribute("value");
			}
			return parametersMap;
		}
		
		private function createDummyInstance(constructorNode:XML, clazz:Class):void
		{
			try
			{
				switch (constructorNode.children().length())
				{
					case 0:new clazz(); break;
					case 1:new clazz(null); break;
					case 2:new clazz(null, null); break;
					case 3:new clazz(null, null, null); break;
					case 4:new clazz(null, null, null, null); break;
					case 5:new clazz(null, null, null, null, null); break;
					case 6:new clazz(null, null, null, null, null, null); break;
					case 7:new clazz(null, null, null, null, null, null, null); break;
					case 8:new clazz(null, null, null, null, null, null, null, null); break;
					case 9:new clazz(null, null, null, null, null, null, null, null, null); break;
					case 10:new clazz(null, null, null, null, null, null, null, null, null, null); break;
					default:new clazz(); break;
				}
			}
			catch (err:Error)
			{
				trace("createDummyInstance" + err);
			}
			constructorNode.setChildren(describeType(clazz).factory.constructor[0].children());
		}
		
		private function gatherMethodParameters(parameterNodes:XMLList, parameterNames:Array):GatherMethodParametersReturn
		{
			var requiredParameters:int = 0;
			const length:int = parameterNodes.length();
			const parameters:Array = new Array(length);
			for (var i:int = 0; i < length; i++) 
			{
				const parameter:XML = parameterNodes[i];
				const injectionName:String = parameterNames[i] || "";
				var parameterTypeName:String = parameter.@type;
				const optional:Boolean = parameter.@optional == "true";
				if (parameterTypeName == "*")
				{
					if (!optional)
					{
						throw new Error("gatherMethodParameters:" + _currentFactoryXML.@type);
					}
					else
					{
						parameterTypeName = null;
					}
				}
				if (!optional)
				{
					requiredParameters++;
				}
				parameters[i] = parameterTypeName + "|" + injectionName;
			}
			const returnGMP:GatherMethodParametersReturn = new GatherMethodParametersReturn();
			returnGMP.parameters = parameters;
			returnGMP.requiredParameters = requiredParameters;
			return returnGMP;
		}
		
		private function gatherOrderedInjectionPointsForTag(injectionPointType:Class, tag:String):Array
		{
			const injectionPoints:Array = [];
			for each(var node:XML in _currentFactoryXML..metadata.(@name == tag))
			{
				const injectParameters:Dictionary = extractNodeParameters(node.arg);
				const parameterNames:Array = (injectParameters.name || "").split(",");
				const returnGMP:GatherMethodParametersReturn = gatherMethodParameters(node.parent().parameter, parameterNames);
				const parameters:Array = returnGMP.parameters;
				const requiredParameters:int = returnGMP.requiredParameters;
				const order:Number = parseInt(node.arg.(@key == "order").@value);
				injectionPoints.push(new injectionPointType(node.parent().@name, parameters, requiredParameters, isNaN(order)?int.MAX_VALUE:order));
			}
			if (injectionPoints.length > 0)
			{
				injectionPoints.sortOn("order", Array.NUMERIC);
			}
			return injectionPoints;
		}
		
		/* INTERFACE xxl.ioc.reflection.IReflector */
		
		public function typeImplements(type:Class, superType:Class):Boolean
		{
			if (type == superType)
			{
				return true;
			}
			const factoryNode:XMLList = describeType(type).factory;
			const superclassName:String = getQualifiedClassName(superType);
			return factoryNode.extendsClass.@type.contains(superclassName)
					|| factoryNode.implementsInterface.@type.contains(superclassName);
		}
		
		public function describeInjections(type:Class):TypeDescription
		{
			_currentFactoryXML = describeType(type).factory[0];
			const description:TypeDescription = new TypeDescription(false);
			addCtorInjectionPoint(description, type);
			addFieldInjectionPoints(description);
			addMethodInjectionPoints(description);
			addPostConstructMethodPoints(description);
			addPreDestroyMethodPoints(description);
			_currentFactoryXML = null;
			return description;
		}
	}

}

class GatherMethodParametersReturn
{
	public var requiredParameters:int;
	public var parameters:Array;
}